<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise的理解和使用</title>
</head>

<body>
    <!-- 
        Promise是什么？
        1. 理解
            1. 抽象的
                - Promise是一门新的技术(ES6规范)
                - Promise是JS中进行异步编程的新解决方案（旧方案是单纯的使用回调函数）
            2. 具体的
                - 从语法上来说： Promise是一个构造函数
                - 从功能上来说： Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
            * **异步编程：**
                - fs 文件操作
                    ```
                    require('fs').readFile('./1Promise理解和使用.html', (err, data) => {})
                    ```
                - 数据库操作

                - AJAX
                    ```
                    // 使用JQ举例
                    $.get('/test', (data) => {})
                    ```
                - 定时器
                    ```
                    setTimeout(() => {}, 2000)
                    ```
        2. promise的状态改变
            * promise状态：是promise实例对象的一个属性，PromiseState
                - 值：
                    1. pending 未决定的
                    2. resolved / fullfilled 表成功
                    3. rejected 表失败
            * promise对象的值： PromiseResult
                - 保存着异步任务【成功/失败】的结果
                    - resolve / reject 函数可以修改PromiseResult的值 
            1. pending变为resolved
            2. pending变为rejected
            - **说明：** 只有这两种情况，且一个promise对象只能改变一次。无论变为成功还是失败，都会有一个结果数据。成功的结果数据一般成为value，失败的结果数据一般称为reason
        3. promise基本流程
            new Promise() ==> 执行异步操作==>成功，执行resolve() ==> promise对象（resolved状态） ==> then()（回调onResolved()） ==> 新的Promise对象
                                       |==>失败，执行reject() ==> promise对象（rejected状态） ==> then/catch()（回调onRejected()）==> 新的Promise对象
        4. 为什么要用Promise
            1. 指定回调函数的方式更加灵活
                1. 旧方案： 必须在启动异步任务之前指定回调函数
                2. promise： 启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数（甚至可以在异步任务结束后指定）
            2. 支持链式调用，可以解决回调地狱问题
                1. 什么是回调地狱？
                    - 回调函数嵌套调用，外部回调函数异步执行的结果是嵌套的回调执行的条件
                    ```
                    // 代码不断向前缩进
                    asyncFunc1(opt, (...args1) => {
                        asyncFunc2(opt, (...args2) => {
                            asyncFunc3(opt, (...args3) => {
                                asyncFunc4(opt, (...args4) => {
                                    // some operation
                                })
                            })
                        })
                    })
                    ```
                2. 回调地狱的缺点？
                    - 不便于阅读
                    - 不便于异常处理
                3. 解决方案？
                    - promise链式调用
        5. 如何使用Promise
            1. Promise构造函数：Promise(exutor){}
                - executor函数： 执行器 (resolve, reject) => {}
                - resolve函数： 内部定义成功时我们调用的函数 
                - reject函数： 内部定义失败是我们调用的函数
                - **说明：** executor会在Promise内部立即执行同步调用，异步操作在执行器中执行
            2. Promise.prototype.then 方法： (onResolved, onRejected) => {}
                - onResolved函数：成功执行的回调函数
                - onRejected函数：失败的回调函数
                - **说明：** 指定用于得到成功结果的成功回调和用于得到失败结果的失败回调返回一个行的Promise对象
            3. Promise.prototype.catch 方法： (onRejected) => {}
                - onRejected函数：失败的回调函数
            4. Promise.resolve方法：(value) => {}
                - value: 成功的数据或promise对象
                - **说明** 返回一个成功/失败的promise对象
                - 如果传入的参数为非promise对象，那么返回的结果为成功的promise对象
                - 如果传入的参数为promise对象，那么参数的结果决定了resolve的结果
            5. Promise.reject方法：(reason) => {}
                - reason:失败的原因
                - **说明：** 返回一个失败的promise对象
                - 无论参数是什么，都会返回一个失败的结果，即使传入的是成功的Promise对象，返回的结果依然是失败的Promise对象，这个失败的对象的值就是参数的值
            6. Promise.all方法：(promises) => {}
                - promises: 包含n个promise的数组
                - **说明：** 返回一个新的promise，只有所有的promise都成功才成功，只要有一个失败了就直接失败
                - 成功的结果值是所有成功的promise对象的结果值组成的数组，失败的结果值就是第一个失败的promise对象的结果
            7. Promise.race方法：(promises) => {}
                - promises：包含n个promise的数组
                - **说明：**返回一个新的promise，第一个完成的promise的结果状态就是最终的结果状态
        6. promise的几个关键问题
            1. 如何改变promise的状态？
                - resolve(value): 如果当前是pending就会变为resolved
                - reject(reason): 如果当前是pending就会变为rejected
                - 抛出异常：如果当前是pending就会变为rejected
            2. 一个promise指定多个成功/失败回调函数，都会调用吗？(指定回调函数的意思就是调用then()或者catch()方法)
                - 当promise改变为对应状态时都会调用
            3. 改变promise状态和指定回调函数谁先谁后？
                - 都有可能，正常情况下是先指定回调再改变状态（当执行器内部是一个异步任务的时候，就相当于先指定了回调函数），但是也可以先改变状态再指定回调函数
                - 如何先改变状态再指定回调？（执行器内部是一个同步任务）
                    1. 在执行器中直接调用resolve()/reject()方法
                    2. 延迟更长时间才调用then()
                - 什么时候才能得到数据？
                    1. 如果先制定的回调，那当状态发生改变时，回调函数就会调用，得到数据
                    2. 如果先改变的状态，那当指定回调时，回调函数就会调用，得到数据
            4. promise.then()返回的新promise的结果状态由什么决定？
                - 简单表达：由then()指定的回调函数执行的结果决定
                - 详细表达：
                    1. 如果抛出异常，新promise变为rejected，reason为抛出的异常
                    2. 如果返回的是非promise的任意值，新promise变为resolved，value为返回的值
                    3. 如果返回的是另一个新promise，此promise的结果就会成为新promise的结果
            5. promise如何串连多个操作任务？
                - promise的then()返回一个新的promise，可以使用then()的链式调用
                - 通过then的链式调用串联多个同步/异步任务
            6. promise一场穿透？
                - 当使用promise的then链式调用时，可以在最后指定失败的回调
                - 前面任何操作出了异常，都会传到最后失败的回调中处理
            7. 中断promise链?
                - 当使用promise的then链式调用时,在中间中断,不在调用后面的回调函数
                - 方法:在回调函数中返回一个pending状态的promise对象(唯一方法:return new Promise(() => {}))
     -->
    <div class="container">
        <h2 class="page-header">Promise初体验</h2>
        <button class="btn btn-primary" id="btn">抽奖</button>
    </div>
    <div class="container">
        <h2 class="page-header">Promise封装Ajax</h2>
        <button class="btn btn-promary" id="btn2">获取段子</button>
    </div>
    <script>
        // promise的使用--模拟抽奖（中奖概率为30%）


        // 生成随机数
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1)) + m - 1
        }
        /*
            点击按钮， 1s后显式是否中奖
                若中奖弹出  恭喜！奖品为10W RMB
                若未中奖弹出 再接再厉
        */
        // 获取元素对象
        const btn = document.getElementById('btn')
        // 绑定单击事件
        btn.addEventListener('click', () => {
            // 定时器，延时1s模拟向服务器发送请求
            // 使用旧方案
            /* setTimeout(() => {
                // 获取一个随机数（1-100）
                let n = rand(1, 100)
                // 判断是否中奖
                if (n <= 30) {
                    // 中奖
                    alert('恭喜！奖品为10W RMB。您的中奖数字为' + n)
                } else {
                    alert('再接再厉！')
                }
            }, 1000) */


            // 使用Promise方案
            const pro = new Promise((resolve, reject) => {
                setTimeout(() => {
                    // 获取一个随机数（1-100）
                    let n = rand(1, 100)
                    // 判断是否中奖
                    if (n <= 30) {
                        // 中奖
                        resolve(n)
                    } else {
                        reject(n)
                    }
                }, 1000)
            })
            pro.then((res) => {
                console.log(`恭喜！奖品为10W RMB。您的中奖号码为${res}`)
            }).catch((err) => {
                console.log(`再接再厉！您的号码为：${err}`)

            })
        })

        // Promise封装AJAX
        // 获取btn2
        const btn2 = document.getElementById('btn2')
        btn2.addEventListener('click', () => {
            // 1. 创建ajax对象
            // 旧方案
            /* const xhr = new XMLHttpRequest()
            xhr.responseType = 'json'
            xhr.open('GET', 'https://api.openapi.top/getJoke')
            xhr.send()
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        console.log(xhr.response)
                    } else {
                        console.log(xhr.status)
                    }
                }
            } */

            // Promise方式
            const pro = new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest()
                xhr.responseType = 'json'
                xhr.open('GET', 'https://api.openapi.top/getJoke')
                xhr.send()
                xhr.onreadystatechange = () => {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(xhr.response)
                        } else {
                            reject(xhr.status)
                        }
                    }
                }
            })
            // 调用then方法处理请求结果
            pro.then((res) => {
                console.log(res)
            }, (err) => {
                console.warn(err)
            })
        })

        // Promise的resolve方法
        // 返回一个成功的promise对象
        let promise1 = Promise.resolve(520)
        console.log(promise1)
        // - 如果传入的参数为非promise对象，那么返回的结果为成功的promise对象
        // - 如果传入的参数为promise对象，那么参数的结果决定了resolve的结果
        let promise2 = Promise.resolve(new Promise((resolve, reject) => {
            // 通过调试发现，resolve, reject这两个方法只能执行第一个
            // reject('Error')
            resolve(521)
        }))
        // 通过.then()来处理返回结果
        promise2.then((res) => {
            console.log(res)
        })
        console.log(promise2)

        // Promise.reject
        let rej = Promise.reject(520)
        // 传入一个成功的Promise对象，其结果依然是失败的Promise对象
        let reject = Promise.reject(new Promise((resolve, reject) => {
            resolve(521)
        }))
        reject.then((res) => {
            console.log(res)
        })

        // Promise.all
        let all1 = Promise.resolve(520)
        let all2 = Promise.resolve(521)
        // let all3 = Promise.resolve(522)
        let all3 = Promise.reject(522)
        // let all4 = Promise.resolve(523)
        let all4 = Promise.reject(523)
        let result = Promise.all([all1, all2, all3, all4])
        console.log('result:', result)

        // Promise对象状态改变的方式
        let status = new Promise((resolve, reject) => {
            // 1. resolve函数
            // 状态变化：pending => resolve
            // resolve(521)
            // 2. reject函数
            // 状态变化： pending => rejected
            // reject('error')
            // 3. 抛出错误
            // 状态变化： pending => rejected
            throw '发生错误'
        })
        console.log(status)

        // Promise指定多个回调函数
        let reCall1 = new Promise((resolve, reject) => {
            // 改变状态
            // 如果状态不发生改变，then()方法就不会执行
            resolve(520)
        })
        reCall1.then((res) => {
            console.log(res)
        })

        // promise的then()方法的返回结果
        let then = new Promise((resolve, rejcet) => {
            resolve(521)
        })

        let thenResult = then.then(value => {
            // console.log(value)
            // 1. 抛出错误
            // throw '发生错误'
            // 2. 返回结果是非Promise类型的对象
            // return 521
            // 3. 返回结果是Promise对象
            return new Promise((resolve, reject) => {
                // resolve(520)
                reject(521)
            })

        }, reason => {
            console.log(reason)
        })
        console.log(thenResult)

        // Promise的链式调用
        let chain = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK')
            }, 1000)
        })

        chain.then(value => {
            return new Promise((resolve, reject) => {
                resolve('success')
            })
        }).then(value => {
            // 输出success
            console.log(value)
        }).then(value => {
            // 输出undefined,上一个promise的值已经没了
            console.log(value)
        })
    </script>
</body>

</html>